﻿#pragma once

#include <string.h>

namespace fast_io::mp3
{

inline constexpr ::std::uint_least32_t decode_mp3_safe_int(::std::uint_least32_t nval) noexcept
{
	// nVal has 4 bytes (8-bits each)
	//  - discard most significant bit from each byte
	//  - reverse byte order
	//  - concatenate the 4 * 7-bit nibbles into a 24-bit size.
	return static_cast<::std::uint_least32_t>(nval & UINT32_C(0x7F)) +
		   static_cast<::std::uint_least32_t>(static_cast<::std::uint_least32_t>(nval >> 8) & UINT32_C(0x7C)) +
		   static_cast<::std::uint_least32_t>(static_cast<::std::uint_least32_t>(nval >> 16) & UINT32_C(0x7C)) +
		   static_cast<::std::uint_least32_t>(static_cast<::std::uint_least32_t>(nval >> 24) & UINT32_C(0x7C));
}

struct mp3_duration_result
{
	::std::uint_least64_t duration_in_milliseconds{}; // milliseconds
	::fast_io::parse_code code{::fast_io::parse_code::ok};
};

inline mp3_duration_result compute_mp3_duration(void const *first, void const *last) noexcept
{
	::std::byte const *firstbyte{reinterpret_cast<::std::byte const *>(first)};
	::std::byte const *lastbyte{reinterpret_cast<::std::byte const *>(last)};
	constexpr ::std::size_t mp3headersize{10};
	if (static_cast<::std::size_t>(lastbyte - firstbyte) < mp3headersize)
	{
		return {0, ::fast_io::parse_code::end_of_file};
	}
	if (::memcmp(firstbyte, u8"ID3", 3) != 0)
	{
		return {0, ::fast_io::parse_code::end_of_file};
	}
	firstbyte += mp3headersize;
	::std::uint_least8_t mp3_header_flag;
	::memcpy(__builtin_addressof(mp3_header_flag), firstbyte + 6, 1);
	constexpr ::std::size_t mp3extendedheadersize{6};
	if (mp3_header_flag & UINT32_C(0x40))
	{
		if (static_cast<::std::size_t>(lastbyte - firstbyte) < mp3extendedheadersize)
		{
			return {0, ::fast_io::parse_code::end_of_file};
		}
		firstbyte += mp3extendedheadersize;
	}
	constexpr ::std::uint_least32_t mp3frameheadersize{10};

	for (; mp3frameheadersize < static_cast<::std::size_t>(lastbyte - firstbyte);)
	{
		::std::uint_least32_t nval;
		::memcpy(__builtin_addressof(nval), firstbyte + 4, 4);
		::std::uint_least32_t datalen{decode_mp3_safe_int(nval)};
		firstbyte += mp3frameheadersize;
		if (static_cast<::std::size_t>(lastbyte - firstbyte) < datalen)
		{
			return {0, ::fast_io::parse_code::invalid};
		}
		char8_t flag0;
		::memcpy(__builtin_addressof(flag0), firstbyte + 8, 1);
		if (!::fast_io::char_category::is_c_upper(flag0))
		{
			return {0, ::fast_io::parse_code::ok};
		}

		if (::memcmp(firstbyte, u8"TLEN", 4) == 0)
		{
			if (static_cast<::std::size_t>(lastbyte - firstbyte) < mp3frameheadersize)
			{
				return {0, ::fast_io::parse_code::end_of_file};
			}
			firstbyte += 4;
			::std::uint_least32_t tlen;
			using char8constptr
#if __has_cpp_attribute(__gnu__::__may_alias__)
				[[__gnu__::__may_alias__]]
#endif
				= char8_t const *;
			auto code = ::fast_io::parse_by_scan(reinterpret_cast<char8constptr>(firstbyte),
												 reinterpret_cast<char8constptr>(lastbyte), tlen)
							.code;
			if (code != ::fast_io::parse_code::ok)
			{
				return {0, code};
			}
			return {tlen};
		}
		else
		{
			firstbyte += datalen;
		}
	}
	return {0, ::fast_io::parse_code::ok};
}

} // namespace fast_io::mp3
